package de.hs_mannheim.srn.prototyp.model;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;


public class User {
	private String name;
	private int hash;
	private byte[] encryptedPrivateKey;
	private PublicKey publicKey;
	private Keybox keybox;
	
	/* Getter & Setter */
	/**
	 * Returns the unique name of this user
	 * @return the unique name of this user
	 */
	public String getName() {
		return this.name;
	}
	/**
	 * Returns the public key of this user
	 * @return public key of this user
	 */
	public PublicKey getPublicKey() {
		return this.publicKey;
	}
	/**
	 * Returns the private key of the user.
	 * Password is needed to decrypt the private key.
	 * If the password is wrong, null will be returned!
	 * @param password for decrypting the private key
	 * @return the private key or null if passoword was wrong
	 */
	public PrivateKey getPrivateKey(String password) {
		String phrase = this.name + password;
		if(phrase.hashCode() == this.hash) {
			// Create the symmetric key from password
			SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
			Cipher cipher;
			byte[] result;
			try {
				cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
				cipher.init(Cipher.DECRYPT_MODE, key);
				result = cipher.doFinal(encryptedPrivateKey);
			} catch (Exception e) {
				throw new RuntimeException("Spurious serialisation error", e);
			}
			return (PrivateKey) deserialize(result);
		}
		return null;
	}
	/**
	 * Returns the keybox of the user
	 * @return
	 */
	public Keybox getKeybox() {
		// TODO: Maybe we can create an addKey()-Method for the user and hide the keybox
		return this.keybox;
	}
	
	
	/* Constructor */
	/**
	 * Initializes a new user with given name.
	 * Don't forget to call the function generate() to
	 * create a public and private key and to generate
	 * and get an personal password for the user.
	 */
	public User(String name) {
		this.name = name;
	}
	
	
	/* Methods */
	/**
	 * Generate all variables of this user and returns a generated password.
	 * This password is never saved locally so it should be noticed in the users brain :-)
	 * @return generated password
	 */
	public String generate() {
		KeyPairGenerator kpg;
		try {
			kpg = KeyPairGenerator.getInstance("RSA");
		} catch (Exception e) {
			throw new RuntimeException("Spurious encryption mode select error", e);
		}
		kpg.initialize(2048);
		KeyPair kp = kpg.genKeyPair();
		this.publicKey = kp.getPublic();
		PrivateKey privateKey = kp.getPrivate();
		byte[] data = serialize(privateKey);
		String password = this.generateRandomPassword(8);
		SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
		Cipher cipher;
		byte[] result;
		try {
			cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
			cipher.init(Cipher.ENCRYPT_MODE, key);
			result = cipher.doFinal(data);
		} catch (Exception e) {
			throw new RuntimeException("Spurious serialisation error", e);
		}
		this.encryptedPrivateKey = result;
		// At last: Generate a (new) keybox
		this.keybox = new Keybox();
		return password;
	}
	@Override
	public boolean equals(Object obj) {
		User user;
		if (obj instanceof User){
			user = (User) obj;
		} else { 
			return false;
		}

		if (!(this.getName().equalsIgnoreCase(user.getName()))){
			return false;
		}

		return true;
	}
	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + this.name.hashCode();
		return result;
	}
	/**
	 * Serializes the given Object to byte-code
	 * @param obj Object to serialize
	 * @return serialized object as an byte-array
	 */
	private byte[] serialize(Object obj) {
	    ByteArrayOutputStream out = new ByteArrayOutputStream();
	    ObjectOutputStream os;
		try {
			os = new ObjectOutputStream(out);
			os.writeObject(obj);
			os.close();
		} catch (IOException e) {
			throw new RuntimeException("Spurious serialisation error", e);
		}
	    return out.toByteArray();
	}
	/**
	 * Deserializes the given byte-code into an object
	 * @param data byte-array to deserialize
	 * @return deserialized byte-code as object
	 */
	private Object deserialize(byte[] data) {
	    ByteArrayInputStream in = new ByteArrayInputStream(data);
	    ObjectInputStream is;
	    Object object;
		try {
			is = new ObjectInputStream(in);
			object = is.readObject();
			is.close();
		} catch (IOException e) {
			throw new RuntimeException("Spurious serialisation error", e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Spurious object casting error", e);
		}
	    return object;
	}
	/**
	 * Generates a ASCII-Password with the given length
	 * @param length of the password
	 * @return the generated password
	 */
	private String generateRandomPassword(int length) {
		String password = "";
		for(int i=0; i < length; i++) {
			password += (char) ( ((int) (Math.random() * 1024))%256 );
		}
		return password;
	}
}
